package net.xiake6.orm.datasource.sharding;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * 通过SQL中解析出全部的表名，以及替换SQL语句的表名为分表表名等功能
 * ClassName TableParser.java
 * 
 * @author fenglibin
 * @Blog http://xiake6.net
 * @Date 2019年12月9日
 * 
 *       Description
 */
public class TableParser {

	/**
	 * 解析SQL，并获取SQL中的所有表名<br>
	 * 注：表名只支持英文大小写字符、数字和下划线，其它的字符暂未支持
	 * 
	 * @param sql
	 * @return
	 */
	public static List<String> getTableListFromSql(String originalSql) {
		List<String> tableList = getTableListFromSql(TableSpiteKeyWord.FROM, originalSql);
		tableList.addAll(getTableListFromSql(TableSpiteKeyWord.INTO, originalSql));
		tableList.addAll(getTableListFromSql(TableSpiteKeyWord.UPDATE, originalSql));
		return tableList;
	}

	/**
	 * 解析SQL，并获取SQL中的所有表名<br>
	 * 注：表名只支持英文大小写字符、数字和下划线，其它的字符暂未支持
	 * 
	 * @param keyWord 表名前的关键字，目前支持TableSpiteKeyWord.FROM和TableSpiteKeyWord.INTO
	 * @param sql
	 * @return
	 */
	private static List<String> getTableListFromSql(TableSpiteKeyWord keyword, String originalSql) {
		String sql = originalSql.toUpperCase();
		List<String> tableList = new ArrayList<String>();
		if (sql.indexOf(keyword.name()) < 0) {
			return tableList;
		}
		int index_start = -1;
		while ((index_start = sql.indexOf(keyword.name())) >= 0) {
			String _sql = sql;

			sql = sql.substring(index_start + keyword.name().length());
			originalSql = originalSql.substring(index_start + keyword.name().length());

			// from关键字和前面的字符一定存在一个分隔符（空格或换行符），
			// 如果不是则说明这个"from"不是关键词，可能是一个字段名称，如filed_from
			if (isNumberCharacterUnderline(_sql.charAt(index_start - 1))) {
				continue;
			}
			// 表名和from关键字之间肯定有一个分隔符，如换行符、空格等，如果没有就表示该from后面的应该不是表名
			if (isNumberCharacterUnderline(_sql.charAt(index_start + keyword.name().length()))) {
				continue;
			}

			StringBuilder tableName = new StringBuilder();
			for (int i = 0; i < originalSql.length(); i++) {
				char chr = originalSql.charAt(i);
				if (isNumberCharacterUnderline(chr)) {
					tableName.append(chr);
				} else if (tableName.length() > 0) {
					tableList.add(tableName.toString());
					break;
				} else if (chr == 40 && tableName.length() == 0) {// 第一个字符是"("，表示当前查询的表名为子查询，跳过
					break;
				}

			}
			if (tableName.length() == 0) {
				continue;
			}
		}
		return tableList;
	}

	/**
	 * 判断当前字符是否数字、大写字母、小写字母或下划线
	 * 
	 * @param chr
	 * @return
	 */
	public static boolean isNumberCharacterUnderline(char chr) {
		if ((chr >= 48 && chr <= 57) || (chr >= 65 && chr <= 90) || (chr >= 97 && chr <= 122) || chr == 95) {
			return true;
		}
		return false;
	}
	
	/**
	 * 将原始SQL中的表名替换为带序号的分表<br>
	 * 注：表名只支持英文大小写字符、数字和下划线，其它的字符暂未支持
	 * 
	 * @param originalSql      需要被替换分表的SQL
	 * @param shardingTableSet 需要分表的表名列表
	 * @param tableShadingId   当前的分表序号
	 * @return 将原始SQL中的表名替换为带序号的分表后的SQL
	 */
	public static String getShardingTableSql(String originalSql, Set<String> shardingTableSet, int tableShadingId) {
		originalSql = getShardingTableSql(TableSpiteKeyWord.FROM, originalSql, shardingTableSet, tableShadingId);
		originalSql = getShardingTableSql(TableSpiteKeyWord.INTO, originalSql, shardingTableSet, tableShadingId);
		originalSql = getShardingTableSql(TableSpiteKeyWord.UPDATE, originalSql, shardingTableSet, tableShadingId);
		return originalSql;
	}

	/**
	 * 将原始SQL中的表名替换为带序号的分表<br>
	 * 注：表名只支持英文大小写字符、数字和下划线，其它的字符暂未支持
	 * 
	 * @param originalSql      需要被替换分表的SQL
	 * @param shardingTableSet 需要分表的表名列表
	 * @param tableShadingId   当前的分表序号
	 * @return 将原始SQL中的表名替换为带序号的分表后的SQL
	 */
	private static String getShardingTableSql(TableSpiteKeyWord keyword,String originalSql, Set<String> shardingTableSet, int tableShadingId) {

		StringBuilder resultSql = new StringBuilder();

		String sql = originalSql.toUpperCase();
		int index_start = -1;
		while ((index_start = sql.indexOf(keyword.name())) >= 0) {
			int result_start = 0;
			String _sql = sql;
			sql = sql.substring(index_start + keyword.name().length());
			resultSql.append(originalSql.substring(0, index_start + keyword.name().length()));
			originalSql = originalSql.substring(index_start + keyword.name().length());

			// from关键字和前面的字符一定存在一个分隔符（空格或换行符），
			// 如果不是则说明这个"from"不是关键词，可能是一个字段名称，如filed_from
			// 但是在update语句中，update关键词前面是没有其它符合号，因而只有在index_start>0时去判断
			if (index_start>0 && isNumberCharacterUnderline(_sql.charAt(index_start - 1))) {
				continue;
			}
			// 表名和from关键字之间肯定有一个分隔符，如换行符、空格等，如果没有就表示该from后面的应该不是表名
			if (isNumberCharacterUnderline(_sql.charAt(index_start + keyword.name().length()))) {
				continue;
			}

			StringBuilder tableName = new StringBuilder();
			for (int i = 0; i < originalSql.length(); i++) {
				char chr = originalSql.charAt(i);
				if (isNumberCharacterUnderline(chr)) {
					tableName.append(chr);
				} else if (tableName.length() > 0) {
					resultSql.append(tableName);
					if (shardingTableSet.contains(tableName.toString())) {
						resultSql.append("_").append(tableShadingId);
					}
					result_start += tableName.length();
					break;
				} else if (chr == 40 && tableName.length() == 0) {// 第一个字符是"("，表示当前查询的表名为子查询，跳过
					break;
				} else {
					result_start++;
					resultSql.append(chr);
				}
			}
			if (result_start > 0) {
				sql = sql.substring(result_start);
				originalSql = originalSql.substring(result_start);
			}
		}
		resultSql.append(originalSql);
		return resultSql.toString();
	}
}
